package abacab.router;

import abacab.actor.ActorDef;
import abacab.ref.AbstractActorRef;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigException.BadValue;

/**
 * A factory for routers
 */
public abstract class RouterHandler {
	/**
	 * Build the router
	 * 
	 * @param actorRef
	 *          the actor's reference
	 * @param definition
	 *          the actor's definition
	 * @return the router
	 * @throws ConfigException
	 *           if the router could not be built
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <M> Router<M> buildRouter(AbstractActorRef<M> actorRef, ActorDef<M> definition) throws ConfigException {
		Router router = definition.getRouter();
		if (router == null) {
			String name = definition.getRouterName();
			if (name != null) {
				Config routerConfig;
				try {
					routerConfig = actorRef.getSystem().getConfig().getConfig("abacab.routers." + name);
				}
				catch (ConfigException e) {
					throw new ConfigException.NotResolved("The router \"" + name + "\" was not found", e);
				}

				// Get the factory
				String factory = routerConfig.getString("factory");
				Class<RouterFactory> routerClass;
				try {
					routerClass = (Class<RouterFactory>) Class.forName(factory);
				}
				catch (ClassNotFoundException e) {
					throw new BadValue("abacab.routers." + name + ".factory", "The router's factory \"" + factory
							+ "\" does not exist", e);
				}
				catch (ClassCastException e) {
					throw new BadValue("abacab.routers." + name + ".factory", "The router's factory \"" + factory
							+ "\" does not implement \"" + RouterFactory.class.getName() + "\"");
				}

				// Build the factory
				RouterFactory routerFactory;
				try {
					routerFactory = routerClass.newInstance();
				}
				catch (Exception e) {
					throw new BadValue("abacab.routers." + name + ".factory", "The router's factory \"" + factory
							+ "\" can not be built", e);
				}
				router = routerFactory.buildRouter(routerConfig);
			}
			else {
				router = new NoRouter<M>();
			}
		}
		router.init(actorRef, definition);
		return router;
	}
}